package com.qkun.library.utils;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.text.Layout;
import android.text.NoCopySpan;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;

import com.qkun.library.base.BaseApplication;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

/**
 * @author qinkun
 * @date 11/19 0019 23:19
 * @description
 */
public final class SpannableStringEx extends SpannableString {

    //前后都包括，在指定范围前后插入新字符，都会应用新样式
    public static final int FLAG_IN_IN = Spannable.SPAN_INCLUSIVE_INCLUSIVE;
    //前面包括，后面不包括
    public static final int FLAG_IN_EX = Spannable.SPAN_INCLUSIVE_EXCLUSIVE;
    //前后都不包括，在指定范围前后插入新字符，样式无变化
    public static final int FLAG_EX_EX = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE;
    //后面包括，前面不包括
    public static final int FLAG_EX_IN = Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
    private final List<Object> mSpans = new ArrayList<>();

    /**
     * For the backward compatibility reasons, this constructor copies all spans including {@link
     * NoCopySpan}.
     *
     * @param source source text
     */
    SpannableStringEx(CharSequence source) {
        super(source);
    }

    @Override
    public void setSpan(Object what, int start, int end, int flags) {
        mSpans.add(what);
        super.setSpan(what, start, end, flags);
    }

    @Override
    public void removeSpan(Object what) {
        mSpans.remove(what);
        super.removeSpan(what);
    }

    public SpannableStringEx removeSpan(int index) {
        if (CollectionsEx.isIncludeIndex(mSpans, index)) {
            removeSpan(mSpans.get(index));
        }
        return this;
    }

    public Object getSpan(int index) {
        if (CollectionsEx.isIncludeIndex(mSpans, index)) {
            return mSpans.get(index);
        }
        return null;
    }

    /**
     * 文本颜色
     *
     * @param color
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addForegroundColor(@ColorInt int color, int start, int end, int flags) {
        setSpan(new ForegroundColorSpan(color), start, end, flags);
        return this;
    }

    public SpannableStringEx addForegroundColorRes(@ColorRes int colorId, int start, int end, int flags) {
        setSpan(new ForegroundColorSpan(Utils.getColor(colorId)), start, end, flags);
        return this;
    }

    /**
     * 文本背景颜色
     *
     * @param color
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addBackgroundColor(@ColorInt int color, int start, int end, int flags) {
        setSpan(new BackgroundColorSpan(color), start, end, flags);
        return this;
    }

    public SpannableStringEx addBackgroundColorRes(@ColorRes int colorId, int start, int end, int flags) {
        setSpan(new BackgroundColorSpan(Utils.getColor(colorId)), start, end, flags);
        return this;
    }

    /**
     * 字体
     *
     * @param family
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addTypeface(String family, int start, int end, int flags) {
        setSpan(new TypefaceSpan(family), start, end, flags);
        return this;
    }

    /**
     * 字体
     * <p>serif 是有衬线字体，意思是在字的笔画开始、结束的地方有额外的装饰，而且笔画的粗细会有所不同。
     * 相反的，sans serif 就没有这些额外的装饰，而且笔画的粗细差不多。
     * </p>
     * <p>
     * {@link Typeface#DEFAULT}：默认的NORMAL字体对象
     * {@link Typeface#DEFAULT_BOLD}：默认的BOLD字体对象。 注意：根据所安装的字体，这实际上可能不是粗体。 调用getStyle（）可以肯定知道。
     * {@link Typeface#SANS_SERIF}：默认的sans serif字体的NORMAL样式。
     * {@link Typeface#SERIF}：默认衬线字体的NORMAL样式。
     * {@link Typeface#MONOSPACE}：默认等宽字体的NORMAL样式。
     * {@link Typeface.Builder}：可以自定义其他Typeface
     *
     * @param typeface
     * @param start
     * @param end
     * @param flags
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.P)
    public SpannableStringEx addTypeface(@NonNull Typeface typeface, int start, int end, int flags) {
        setSpan(new TypefaceSpan(typeface), start, end, flags);
        return this;
    }

    /**
     * 文本绝对大小
     *
     * @param size
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addAbsoluteSize(int size, int start, int end, int flags) {
        setSpan(new AbsoluteSizeSpan(size), start, end, flags);
        return this;
    }

    public SpannableStringEx addAbsoluteSizeRes(@DimenRes int sizeRes, int start, int end, int flags) {
        setSpan(new AbsoluteSizeSpan(BaseApplication.getAppContext().getResources().getDimensionPixelSize(sizeRes)), start, end, flags);
        return this;
    }

    public SpannableStringEx addAbsoluteSize(int size, boolean dip, int start, int end, int flags) {
        setSpan(new AbsoluteSizeSpan(size, dip), start, end, flags);
        return this;
    }

    /**
     * 文字相对大小（相对于设置的文本字体大小）
     *
     * @param proportion 文本缩放比列
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addRelativeSize(@FloatRange(from = 0) float proportion, int start, int end, int flags) {
        setSpan(new RelativeSizeSpan(proportion), start, end, flags);
        return this;
    }

    /**
     * 样式
     *
     * @param style
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addStyle(@Style int style, int start, int end, int flags) {
        setSpan(new StyleSpan(style), start, end, flags);
        return this;
    }

    /**
     * 删除线
     *
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addStrikethrough(int start, int end, int flags) {
        setSpan(new StrikethroughSpan(), start, end, flags);
        return this;
    }

    /**
     * 下划线
     *
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addUnderline(int start, int end, int flags) {
        setSpan(new UnderlineSpan(), start, end, flags);
        return this;
    }

    /**
     * 文字上标
     *
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addSuperscript(int start, int end, int flags) {
        setSpan(new SuperscriptSpan(), start, end, flags);
        return this;
    }

    /**
     * 文字下标
     *
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addSubscript(int start, int end, int flags) {
        setSpan(new SubscriptSpan(), start, end, flags);
        return this;
    }

    /**
     * 插入图片
     *
     * @param context
     * @param resourceId
     * @param verticalAlignment {@link ImageSpan#ALIGN_BASELINE}、{@link ImageSpan#ALIGN_BOTTOM}、{@link ImageSpan#ALIGN_CENTER}
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addImage(@NonNull Context context, @DrawableRes int resourceId,
                                      int verticalAlignment, int start, int end, int flags) {
        setSpan(new ImageSpan(context, resourceId, verticalAlignment), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Context context, @DrawableRes int resourceId, int start, int end, int flags) {
        setSpan(new ImageSpan(context, resourceId), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@DrawableRes int resourceId,
                                      int verticalAlignment, int start, int end, int flags) {
        setSpan(new ImageSpan(BaseApplication.getAppContext(), resourceId, verticalAlignment), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@DrawableRes int drawableRes, int start, int end, int flags) {
        setSpan(new ImageSpan(BaseApplication.getAppContext(), drawableRes), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Context context, @NonNull Uri uri,
                                      int verticalAlignment, int start, int end, int flags) {
        setSpan(new ImageSpan(context, uri, verticalAlignment), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Context context, @NonNull Uri uri, int start, int end, int flags) {
        setSpan(new ImageSpan(context, uri), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Drawable drawable, @NonNull String source,
                                      int verticalAlignment, int start, int end, int flags) {
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        setSpan(new ImageSpan(drawable, source, verticalAlignment), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Drawable drawable, @NonNull String source, int start, int end, int flags) {
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        setSpan(new ImageSpan(drawable, source), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Drawable drawable, int verticalAlignment, int start, int end, int flags) {
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        setSpan(new ImageSpan(drawable, verticalAlignment), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Drawable drawable, int start, int end, int flags) {
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        setSpan(new ImageSpan(drawable), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Context context, @NonNull Bitmap bitmap, int verticalAlignment, int start, int end, int flags) {
        setSpan(new ImageSpan(context, bitmap, verticalAlignment), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Context context, @NonNull Bitmap bitmap, int start, int end, int flags) {
        setSpan(new ImageSpan(context, bitmap), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Bitmap bitmap, int verticalAlignment, int start, int end, int flags) {
        setSpan(new ImageSpan(bitmap, verticalAlignment), start, end, flags);
        return this;
    }

    public SpannableStringEx addImage(@NonNull Bitmap bitmap, int start, int end, int flags) {
        setSpan(new ImageSpan(bitmap), start, end, flags);
        return this;
    }

    /**
     * 添加点击事件
     * <p>
     * setMovementMethod是重中之重，只有设置了才能有 点击功能
     * 另外，如果你需要设置文字的各种样式，
     * 比如：取消下划线<doce>ds.setUnderlineText(false)</doce>，改变字体颜色等
     * 请在{@link ClickableSpan#updateDrawState}方法里设置
     * </p>
     *
     * @param clickableSpan
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addClick(@NonNull ClickableSpan clickableSpan, int start, int end, int flags) {
        setSpan(clickableSpan, start, end, flags);
        return this;
    }

    /**
     * 添加超链接
     *
     * <p>使用超链接，依然要设置setMovementMethod，另外URLSpan也是可以重写点击事件的</p>
     *
     * @param url
     * @param start
     * @param end
     * @param flags
     * @return
     */
    public SpannableStringEx addURL(String url, int start, int end, int flags) {
        setSpan(new URLSpan(url), start, end, flags);
        return this;
    }

    public SpannableStringEx addURL(@NonNull URLSpan urlSpan, int start, int end, int flags) {
        setSpan(urlSpan, start, end, flags);
        return this;
    }

    public void applyTo(@NonNull TextView textView) {
        textView.setText(this);
    }

    public void applyTo(@NonNull TextView textView, @ColorRes int highlightColorRes) {
        applyTo(textView, this, highlightColorRes);
    }

    public static void applyTo(@NonNull TextView textView, @NonNull SpannableString spannableString, @ColorRes int highlightColorRes) {
        applyClickable(textView);
        //修改点击后的背景色
        textView.setHighlightColor(ContextCompat.getColor(BaseApplication.getAppContext(), highlightColorRes));
        textView.setText(spannableString);
    }

    public static void applyClickable(@NonNull TextView textView) {
        //关键,只有TextView添加这个后才能有点击事件
        textView.setMovementMethod(LinkMovementMethod.getInstance());
        //解决添加了clickSpan后仍相应点击事件bug
        textView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                TextView tv = (TextView) v;
                CharSequence text = tv.getText();
                if (text instanceof SpannableString) {
                    if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP) {
                        int x = (int) event.getX();
                        int y = (int) event.getY();
                        x -= tv.getTotalPaddingLeft();
                        y -= tv.getTotalPaddingTop();
                        x += tv.getScrollX();
                        y += tv.getScrollY();

                        Layout layout = tv.getLayout();
                        int line = layout.getLineForVertical(y);
                        int off = layout.getOffsetForHorizontal(line, x);

                        ClickableSpan[] link = ((SpannableString) text).getSpans(off, off, ClickableSpan.class);
                        if (link.length > 0) {
                            if (action == MotionEvent.ACTION_UP) {
                                link[0].onClick(tv);
                            }
                            return true;
                        }
                    }
                }
                return false;
            }
        });
    }

    /**
     * {@link Typeface#NORMAL}：正常
     * {@link Typeface#BOLD}：粗体
     * {@link Typeface#ITALIC}：斜体
     * {@link Typeface#BOLD_ITALIC}：粗体和斜体
     */
    @IntDef(value = {Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Style {
    }

}
